Skip to content

feat(review-suite): add connector-outcome curation and promotion tooling - #87

Merged
shaug merged 3 commits into
mainfrom
claude/56-connector-intake
Jul 29, 2026
Merged

feat(review-suite): add connector-outcome curation and promotion tooling#87
shaug merged 3 commits into
mainfrom
claude/56-connector-intake

Conversation

@shaug

@shaug shaug commented Jul 29, 2026

Copy link
Copy Markdown
Owner

Summary

Builds the intake schema, validator, and promotion workflow for turning
adjudicated connector review outcomes into result-blind regression cases and,
only when evidence warrants it, updates to the global rubric or a
repository-owned instruction file. This is infrastructure only, proven with
synthetic fixtures — it does not source, adjudicate, or import any real
connector finding, and it never touches review-suite/evals/baseline/v1/ or
review-suite/evals/v2/ (frozen artifacts from #58/#59).

  • review-suite/evals/contracts/curation-record.schema.json — one adjudicated
    connector claim: disposition (8-value vocabulary), public/private evidence
    split, and duplicate/unresolved handling.
  • review-suite/evals/contracts/promotion-decision.schema.json — the
    narrowest-owner decision a group of curation records supports: a corpus case
    only, a global rubric change, a repository-instruction change, or nothing.
  • review-suite/evals/contracts/disclosure-guardrail.json +
    curation.disclosure_guardrail_errors — the mechanical disclosure guardrail:
    when a record's provenance.source_class is private_authorized, its
    public-facing source_description must match an allow-listed generic phrase,
    and validation fails closed on a path-like token, a bare hostname-shaped
    token, or a configured (empty-by-default) deny-listed identifier.
  • review-suite/scripts/evals/curation.py + audit_curation.py
    (just audit-review-curation) — the loader/validator, mirroring the existing
    corpus.py/audit_corpus.py pattern.
  • review-suite/evals/curation/ — synthetic positive, negative, duplicate,
    unresolved, restricted-data, and promotion-decision fixtures, including three
    fixtures proving the disclosure guardrail fails closed (path-like token,
    bare hostname, deny-listed identifier), plus a README documenting the
    workflow.
  • 45 new tests in review-suite/scripts/tests/test_eval_curation.py.

Why

Turning real adjudicated connector findings into regression cases or rubric
changes needs a conservative, auditable path so an unresolved or duplicate
claim can never quietly become a gating expectation, and so a disclosure
boundary this repository has already needed elsewhere doesn't rely on
convention alone. This ticket's own body requires the guardrail to be real,
tested, fail-closed code, not a comment — that requirement is what
disclosure_guardrail_errors and its three failing fixtures exist to satisfy.

Test plan

  • just format
  • just lint
  • just test (311 tests, including the 45 new curation tests)
  • python3 review-suite/scripts/evals/audit_curation.py — curation audit
    passed
  • Repository-owned review-code-change (solution-simplicity,
    correctness, code-simplicity) clean at this head

Fixes #56

shaug and others added 3 commits July 29, 2026 07:39
## Summary
- Add `curation-record.schema.json` and `promotion-decision.schema.json`
  under `review-suite/evals/contracts/`, plus `curation.py` and
  `audit_curation.py` (`just audit-review-curation`) to load and validate
  them, mirroring the existing corpus/expectation/provenance pattern.
- Add the mechanical disclosure guardrail: when a curation record's
  `provenance.source_class` is `private_authorized`, its public-facing
  `source_description` must match an allow-listed generic phrase in the new
  `disclosure-guardrail.json`, and validation fails closed on a path-like
  token, a bare hostname-shaped token, or a configured deny-listed
  identifier. The shipped deny-list is empty by design.
- Enforce disposition semantics (accepted/rejected/deferred/duplicate/
  unresolved validate distinctly), reviewer/private text separation, and
  promotion-decision rules: unresolved and plain-duplicate records can never
  be promoted, a global rubric change needs two representative positives
  plus a negative control, and a repository-instruction change may only
  target an existing repository-owned instruction file (AGENTS.md or
  CLAUDE.md) - no new shared path-rule subsystem.
- Add synthetic positive, negative, duplicate, unresolved, restricted-data,
  and promotion-decision fixtures under `review-suite/evals/curation/`,
  including three fixtures proving the disclosure guardrail fails closed
  (path-like token, bare hostname, deny-listed identifier), plus 39 new
  tests in `test_eval_curation.py`.
- Document the workflow in `review-suite/evals/curation/README.md` and the
  root `README.md`.

## Why
- #56 requires a conservative, auditable workflow for turning adjudicated
  connector outcomes into regression cases and, only when evidence
  warrants it, rubric or repository-instruction updates - proven here as
  infrastructure with synthetic fixtures only, per its own non-goals.
- #56's own disclosure boundary requires the mechanical guardrail to be
  real, tested, fail-closed code, not a comment or convention, because
  curation discipline alone has already been shown elsewhere in this
  repository (`review-suite/evals/baseline/v1/LIMITATIONS.md` items 21-24,
  29-31) not to reliably catch every leak on its own.
- None of this data resembles or is derived from any real private source;
  `baseline/v1/` and `v2/` are untouched.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…icate_of chain

## Summary
- `_promotable_errors` now resolves a `duplicate` record's effective
  disposition by following `duplicate_of` (via new
  `_resolve_duplicate_disposition`, with cycle detection) before allowing it
  to fill a promotion decision's positive or negative slot.
- Add two synthetic fixtures (`cache-ttl-duplicate-with-new-surface`,
  `unresolved-duplicate-claim`) and six regression tests covering: a
  distinct duplicate of an accepted record used as a negative control, of a
  rejected record used as a positive case, of an unresolved claim, a
  multi-hop duplicate chain, and a duplicate cycle.

## Why
- Independent correctness review of this candidate found that a distinct
  duplicate's own disposition is always `duplicate` (never
  `accepted_*`/`rejected_*`), so the prior check let a duplicate of a
  rejected false positive support a positive case, or a duplicate of an
  accepted defect support a negative control - feeding a rejected
  non-finding into a rubric change as if it were a real defect, or the
  reverse. Resolving through the chain closes that gap without weakening
  the existing double-counting protection for a plain duplicate.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… its membership check

## Summary
- `_resolve_duplicate_disposition` is now an iterative loop with a local
  `seen` set instead of self-recursion carrying a `_seen` frozenset
  accumulator parameter. Same behavior (including cycle detection), simpler
  control flow.
- `_promotable_errors` now computes one `(effective_id, effective_disposition)`
  pair after the duplicate-only early returns, then runs the
  accepted/rejected disposition-membership check exactly once, selecting the
  existing error-message wording only on whether `effective_id == record_id`.
  Previously the same membership rule was checked twice: once for a
  duplicate's resolved disposition, once for a direct record's own
  disposition.
- No behavior change: all 45 existing curation tests pass unmodified,
  including the multi-hop chain and cycle-detection cases.

## Why
- Code-simplicity review of this candidate flagged both as unnecessary local
  complexity: recursion for a simple bounded linear traversal, and
  duplicated membership-check logic that could drift out of sync between its
  two copies.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@shaug
shaug merged commit 16fc32a into main Jul 29, 2026
1 check passed
@shaug
shaug deleted the claude/56-connector-intake branch July 29, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Operationalize adjudicated connector outcomes as review regressions

1 participant